import hashlib
import base64
import json
import arrow
import bson
import inspect

import re

from flask import request, g, jsonify
from urllib.parse import urlparse

from app.libs.error_code import ERROR_CODE_MAP
from app.libs.regexp import SPECIAL_CHA_SET, IPV6_RE, IP_RE, REPLACE_PROTOCOL_RE
from app.libs.enums import NetWorkType
from app.libs import node


def clean_set(s):
    if '' in s:
        s.remove('')
    if None in s:
        s.remove(None)
    return s


def make_cache_key_global(*args, **kwargs):
    if user := kwargs.get("user"):
        md5_digest = hashlib.md5(f"{request.full_path.encode('utf-8')}{str(user.id)}").digest()
    else:
        md5_digest = hashlib.md5(request.full_path.encode('utf-8')).digest()
    cache_key = base64.b64encode(md5_digest)[:16]
    return cache_key.decode('utf-8')


def make_cache_key_uid(*args, **kwargs):
    md5_digest = hashlib.md5((str(g.user.id)+request.full_path).encode('utf-8')).digest()
    cache_key = base64.b64encode(md5_digest)[:16]
    return cache_key.decode('utf-8')


def filter_cha(s0: str):
    for c in SPECIAL_CHA_SET:
        s0 = s0.replace(c, rf'\{c}')

    return s0


def get_request_args(allow_args):
    request_args = {}
    try:
        base_request_args = request.args if request.method == "GET" else json.loads(request.data)
    except Exception:
        return request_args

    for k in allow_args:
        if k in base_request_args:
            v = base_request_args.get(k)
            if k in ('page', 'limit'):
                v = int(v)
            request_args[k] = v

    return request_args


def get_request_args_v3(allow_args):
    request_args = get_request_args(allow_args)
    if request_args.get("limit", 0) > 50:
        request_args["limit"] = 50
    return request_args


def get_page_args(request_args):
    page = int(request_args.get('page')) if request_args.get('page') else 1
    limit = int(request_args.get('limit')) if request_args.get('limit') else 10
    if g.role.name != '管理员':
        limit = 50 if limit > 50 else limit
    return page, limit


def get_flat_dict_value(d0: dict, k: str, default=None):
    temp_k_list = k.split('.')
    if (temp_d := d0.get(temp_k_list[0])) is not None:
        if len(temp_k_list) > 1 and isinstance(temp_d, dict):
            return get_flat_dict_value(temp_d, '.'.join(temp_k_list[1:]), default)

        return temp_d
    else:
        return default


def get_period_stamp():
    return arrow.utcnow().replace(
        day=1, hour=0, minute=0, second=0, microsecond=0).shift(months=1, hours=-8).timestamp()


def trans_interval(interval):
    """

    Args:
        interval:

    Returns: unit: microseconds

    """
    if isinstance(interval, dict):
        num = interval.get('num', 0)
        unit = interval.get('unit', '')
    else:
        num = interval.num
        unit = interval.unit

    if unit == 'd':
        pass
    elif unit in ('w', 'week'):
        num = num * 7
    elif unit == 'month':
        num = 30 * num
    elif unit == 'm':
        return num * 60 * 1000

    return num * 24 * 3600 * 1000


def compared_value(data, gte_value, _type='gt'):
    """
    data [_type] gte_value return false
    """
    if _type == 'gte':
        _bool = bool(data >= gte_value)
    elif _type == 'gt':
        _bool = bool(data > gte_value)
    elif _type == 'lte':
        _bool = bool(data <= gte_value)
    elif _type == 'lt':
        _bool = bool(data < gte_value)
    else:
        _bool = bool(data == gte_value)

    if _bool:
        return False

    return True


def merge_feature(f0, f1, is_all=False):
    keys = f0.keys() if len(f0) > len(f1) else f1.keys()
    if is_all:
        keys = set(list(f0.keys()) + list(f1.keys()))
    new_feature = {}
    for k in keys:
        new_feature[k] = f0.get(k) or f1.get(k, False)
    return new_feature


def get_warning(warning_id: bson.ObjectId, warnings: list):
    for warning in warnings:
        if warning['_id'] == warning_id:
            return warning
    return None


def filter_port_from_url(url: str):
    s0 = urlparse(url)
    if s0.port in (80, 443):
        return f"{s0.scheme}://{s0.hostname}{s0.path}?{s0.query}"

    return url


def list_over_insert(l0: list, index: int, value, default=None):
    d = index - len(l0)
    if d >= 0:
        for i in range(d+1):
            l0.append(default)
    l0[index] = value


def list_protect_get(l0: list, index: int, default=None):
    if index < len(l0):
        return l0[index]
    return default


def get_default_area(target_url: str, source_ip: str) -> dict:
    if not source_ip:
        source_ip = ""
    network_type = get_network_type(target_url, source_ip)

    if network_type == NetWorkType.v4.value:
        return {'area': DEFAULT_AREA_TUPLE, 'area_ipv6': []}
    return {'area': [], 'area_ipv6': DEFAULT_AREA_V6_TUPLE}


def random_one_area(target_url: str, source_ip: str):
    area_map = get_default_area(target_url, source_ip)
    if area_map["area"]:
        return area_map["area"][0]
    if area_map["area_ipv6"]:
        return area_map["area_ipv6"][0]
    return DEFAULT_AREA_TUPLE[0]


def get_node_area_list(network_type=NetWorkType.v4.value):
    area_list = []
    for cls, _ in inspect.getmembers(node, inspect.isclass):
        obj = getattr(node, cls, None)
        if not obj:
            continue
        if issubclass(obj, node.BaseAreaNode) and obj != node.BaseAreaNode and obj.network_type == network_type:
            area_list.append(obj.area)
    return area_list


def get_area_dict():
    v4 = []
    v6 = []
    for cls, _ in inspect.getmembers(node, inspect.isclass):
        obj = getattr(node, cls, None)
        if not obj:
            continue
        if issubclass(obj, node.BaseAreaNode) and obj != node.BaseAreaNode:
            if obj.network_type == node.NetWorkType.v4.value:
                v4.append({obj.area: obj.display_name.get("zh_cn"), "enable": True})
            elif obj.network_type == node.NetWorkType.v6.value:
                v6.append({obj.area: obj.display_name.get("zh_cn"), "enable": True})
            elif obj.network_type == node.NetWorkType.v4v6.value:
                v4.append({obj.area: obj.display_name.get("zh_cn"), "enable": True})
                v6.append({obj.area: obj.display_name.get("zh_cn"), "enable": True})
    return {"v4_area": v4, "v6_area": v6, "default_v4": DEFAULT_AREA_TUPLE, "default_v6": DEFAULT_AREA_V6_TUPLE}


def get_network_type(target_url: str, source_ip: str) -> str:
    network_type = NetWorkType.v4.value
    target_url = re.sub(REPLACE_PROTOCOL_RE, '', target_url)
    if re.match(IPV6_RE, source_ip) or re.match(IPV6_RE, target_url):
        network_type = NetWorkType.v6.value

    return network_type


IPV4_AREA_LIST = get_node_area_list(NetWorkType.v4.value)
IPV6_AREA_LIST = get_node_area_list(NetWorkType.v4v6.value)

DEFAULT_AREA_TUPLE = (
    node.ShiJiaZhuangCmc1.area, node.Chengdu.area, node.Huaian.area, node.Dalian.area, node.Zhuzhou.area)
DEFAULT_AREA_V6_TUPLE = (
    node.Dalian.area, node.Huaian.area, node.Nanchang.area, node.Chengdu.area, node.Tianjin.area)


def trans_textbox_to_dict(text) -> dict:
    d0 = {}
    if not text:
        return d0
    if isinstance(text, dict):
        return text

    for item in text.split('\n'):
        k_v_list = item.split(': ')
        if k_v_list:
            d0[k_v_list[0]] = k_v_list[1]
    return d0


def api_v3_response(status="success", errcode=0, message=None, data=None, message_kwargs=None):
    if not message:
        message = ERROR_CODE_MAP.get(errcode, "操作成功")
    if message_kwargs:
        message = message.format(**message_kwargs)
    data = {"status": status, "errcode": errcode, "message": message, "data": data}
    return jsonify(data)


def str_id_to_object(ids):
    return [bson.ObjectId(i) for i in ids]
